// Enesea Linux Device
// (c) jimon game studio

#include "CDevice_Linux.h"
#include "jeThreads.h"
#include "CMouseDriver.h"
#include "CKeyDriver.h"
#include "ILogManager.h"

#ifdef JE_DEVICE_LINUX

#include <X11/keysym.h>

namespace je
{
	namespace device
	{
		//! Constructor
		CDevice_Linux::CDevice_Linux()
		{
			CurrentDisplay = XOpenDisplay(NULL);

  			if(!CurrentDisplay)
      			JELOG_E("Couldn't connect to X Server");

			KeyMap.set(XK_BackSpace,input::KC_BACK);
			KeyMap.set(XK_Tab,input::KC_TAB);
			KeyMap.set(XK_Clear,input::KC_CLEAR);
			KeyMap.set(XK_Return,input::KC_RETURN);
			KeyMap.set(XK_Pause,input::KC_PAUSE);
			KeyMap.set(XK_Scroll_Lock,input::KC_SCROLL);
			KeyMap.set(XK_Escape,input::KC_ESCAPE);
			KeyMap.set(XK_Insert,input::KC_INSERT);
			KeyMap.set(XK_Delete,input::KC_DELETE);
			KeyMap.set(XK_Home,input::KC_HOME);
			KeyMap.set(XK_Left,input::KC_LEFT);
			KeyMap.set(XK_Up,input::KC_UP);
			KeyMap.set(XK_Right,input::KC_RIGHT);
			KeyMap.set(XK_Down,input::KC_DOWN);
			KeyMap.set(XK_Prior,input::KC_PRIOR);
			KeyMap.set(XK_Page_Up,input::KC_PRIOR);
			KeyMap.set(XK_Next,input::KC_NEXT);
			KeyMap.set(XK_Page_Down,input::KC_NEXT);
			KeyMap.set(XK_End,input::KC_END);
			KeyMap.set(XK_Begin,input::KC_HOME);
			KeyMap.set(XK_KP_Space,input::KC_SPACE);
			KeyMap.set(XK_KP_Tab,input::KC_TAB);
			KeyMap.set(XK_KP_Enter,input::KC_RETURN);
			KeyMap.set(XK_KP_F1,input::KC_F1);
			KeyMap.set(XK_KP_F2,input::KC_F2);
			KeyMap.set(XK_KP_F3,input::KC_F3);
			KeyMap.set(XK_KP_F4,input::KC_F4);
			KeyMap.set(XK_KP_Left,input::KC_LEFT);
			KeyMap.set(XK_KP_Up,input::KC_UP);
			KeyMap.set(XK_KP_Right,input::KC_RIGHT);
			KeyMap.set(XK_KP_Down,input::KC_DOWN);
			KeyMap.set(XK_KP_Prior,input::KC_PRIOR);
			KeyMap.set(XK_KP_Page_Up,input::KC_PRIOR);
			KeyMap.set(XK_KP_Next,input::KC_NEXT);
			KeyMap.set(XK_KP_Page_Down,input::KC_NEXT);
			KeyMap.set(XK_KP_End,input::KC_END);
			KeyMap.set(XK_KP_Begin,input::KC_HOME);
			KeyMap.set(XK_KP_Insert,input::KC_INSERT);
			KeyMap.set(XK_KP_Delete,input::KC_DELETE);
			KeyMap.set(XK_KP_Multiply,input::KC_MULTIPLY);
			KeyMap.set(XK_KP_Add,input::KC_ADD);
			KeyMap.set(XK_KP_Separator,input::KC_SEPARATOR);
			KeyMap.set(XK_KP_Subtract,input::KC_SUBTRACT);
			KeyMap.set(XK_KP_Decimal,input::KC_DECIMAL);
			KeyMap.set(XK_KP_Divide,input::KC_DIVIDE);
			KeyMap.set(XK_KP_0,input::KC_0);
			KeyMap.set(XK_KP_1,input::KC_1);
			KeyMap.set(XK_KP_2,input::KC_2);
			KeyMap.set(XK_KP_3,input::KC_3);
			KeyMap.set(XK_KP_4,input::KC_4);
			KeyMap.set(XK_KP_5,input::KC_5);
			KeyMap.set(XK_KP_6,input::KC_6);
			KeyMap.set(XK_KP_7,input::KC_7);
			KeyMap.set(XK_KP_8,input::KC_8);
			KeyMap.set(XK_KP_9,input::KC_9);
			KeyMap.set(XK_F1,input::KC_F1);
			KeyMap.set(XK_F2,input::KC_F2);
			KeyMap.set(XK_F3,input::KC_F3);
			KeyMap.set(XK_F4,input::KC_F4);
			KeyMap.set(XK_F5,input::KC_F5);
			KeyMap.set(XK_F6,input::KC_F6);
			KeyMap.set(XK_F7,input::KC_F7);
			KeyMap.set(XK_F8,input::KC_F8);
			KeyMap.set(XK_F9,input::KC_F9);
			KeyMap.set(XK_F10,input::KC_F10);
			KeyMap.set(XK_F11,input::KC_F11);
			KeyMap.set(XK_F12,input::KC_F12);
			KeyMap.set(XK_Shift_L,input::KC_LSHIFT);
			KeyMap.set(XK_Shift_R,input::KC_RSHIFT);
			KeyMap.set(XK_Control_L,input::KC_LCONTROL);
			KeyMap.set(XK_Control_R,input::KC_RCONTROL);
			KeyMap.set(XK_Caps_Lock,input::KC_CAPITAL);
			KeyMap.set(XK_Shift_Lock,input::KC_CAPITAL);
			KeyMap.set(XK_Meta_L,input::KC_LWIN);
			KeyMap.set(XK_Meta_R,input::KC_RWIN);
			KeyMap.set(XK_Alt_L,input::KC_LMENU);
			KeyMap.set(XK_Alt_R,input::KC_RMENU);
			KeyMap.set(XK_ISO_Level3_Shift,input::KC_RMENU);
			KeyMap.set(XK_Menu,input::KC_MENU);
			KeyMap.set(XK_space,input::KC_SPACE);
			KeyMap.set(XK_plus,input::KC_PLUS);
			KeyMap.set(XK_comma,input::KC_COMMA);
			KeyMap.set(XK_minus,input::KC_MINUS);
			KeyMap.set(XK_period,input::KC_PERIOD);
			KeyMap.set(XK_0,input::KC_0);
			KeyMap.set(XK_1,input::KC_1);
			KeyMap.set(XK_2,input::KC_2);
			KeyMap.set(XK_3,input::KC_3);
			KeyMap.set(XK_4,input::KC_4);
			KeyMap.set(XK_5,input::KC_5);
			KeyMap.set(XK_6,input::KC_6);
			KeyMap.set(XK_7,input::KC_7);
			KeyMap.set(XK_8,input::KC_8);
			KeyMap.set(XK_9,input::KC_9);
			KeyMap.set(XK_A,input::KC_A);
			KeyMap.set(XK_B,input::KC_B);
			KeyMap.set(XK_C,input::KC_C);
			KeyMap.set(XK_D,input::KC_D);
			KeyMap.set(XK_E,input::KC_E);
			KeyMap.set(XK_F,input::KC_F);
			KeyMap.set(XK_G,input::KC_G);
			KeyMap.set(XK_H,input::KC_H);
			KeyMap.set(XK_I,input::KC_I);
			KeyMap.set(XK_J,input::KC_J);
			KeyMap.set(XK_K,input::KC_K);
			KeyMap.set(XK_L,input::KC_L);
			KeyMap.set(XK_M,input::KC_M);
			KeyMap.set(XK_N,input::KC_N);
			KeyMap.set(XK_O,input::KC_O);
			KeyMap.set(XK_P,input::KC_P);
			KeyMap.set(XK_Q,input::KC_Q);
			KeyMap.set(XK_R,input::KC_R);
			KeyMap.set(XK_S,input::KC_S);
			KeyMap.set(XK_T,input::KC_T);
			KeyMap.set(XK_U,input::KC_U);
			KeyMap.set(XK_V,input::KC_V);
			KeyMap.set(XK_W,input::KC_W);
			KeyMap.set(XK_X,input::KC_X);
			KeyMap.set(XK_Y,input::KC_Y);
			KeyMap.set(XK_Z,input::KC_Z);
			KeyMap.set(XK_a,input::KC_A);
			KeyMap.set(XK_b,input::KC_B);
			KeyMap.set(XK_c,input::KC_C);
			KeyMap.set(XK_d,input::KC_D);
			KeyMap.set(XK_e,input::KC_E);
			KeyMap.set(XK_f,input::KC_F);
			KeyMap.set(XK_g,input::KC_G);
			KeyMap.set(XK_h,input::KC_H);
			KeyMap.set(XK_i,input::KC_I);
			KeyMap.set(XK_j,input::KC_J);
			KeyMap.set(XK_k,input::KC_K);
			KeyMap.set(XK_l,input::KC_L);
			KeyMap.set(XK_m,input::KC_M);
			KeyMap.set(XK_n,input::KC_N);
			KeyMap.set(XK_o,input::KC_O);
			KeyMap.set(XK_p,input::KC_P);
			KeyMap.set(XK_q,input::KC_Q);
			KeyMap.set(XK_r,input::KC_R);
			KeyMap.set(XK_s,input::KC_S);
			KeyMap.set(XK_t,input::KC_T);
			KeyMap.set(XK_u,input::KC_U);
			KeyMap.set(XK_v,input::KC_V);
			KeyMap.set(XK_w,input::KC_W);
			KeyMap.set(XK_x,input::KC_X);
			KeyMap.set(XK_y,input::KC_Y);
			KeyMap.set(XK_z,input::KC_Z);
			// TODO add tilda key here
		}

		//! Destructor
		CDevice_Linux::~CDevice_Linux()
		{
			XCloseDisplay(CurrentDisplay);
		}

		//! Create Window
		IWindow * CDevice_Linux::CreateWindow(const jeVector2di & Size,const jeStringd & Title,u1 FullScreen,s32 Depth,s32 Hertz,s32 MSAA,const void * PlatformIconId)
		{
			CWindow_Linux * Window = NULL;
			JENEW(Window,CWindow_Linux(CurrentDisplay,Size,Title,FullScreen,Depth,Hertz,MSAA))
			WindowMap.set(Window->GetWindow(),Window);
			return Window;
		}

		//! Delete Window
		void CDevice_Linux::DeleteWindow(IWindow * Window)
		{
			for(WindowMapType::Iterator Iter = WindowMap.getIterator();!Iter.atEnd();Iter++)
				if(Iter->getValue()==Window)
				{
					WindowMap.remove(Iter->getKey());
					Window->Drop();
					break;
				}
		}

		//! Device Poll
		void CDevice_Linux::DevicePoll()
		{
			XEvent Event;
			while(XPending(CurrentDisplay)>0)
			{
				XNextEvent(CurrentDisplay,&Event);
				switch(Event.type)
				{
					case ConfigureNotify:
					{
						WindowMapType::Node * Node = WindowMap.find(Event.xconfigure.window);
						if(Node)
							Node->getValue()->Resize(jeVector2di(Event.xconfigure.width,Event.xconfigure.height));
						break;
					}
					case FocusIn:
					case FocusOut:
					{
						WindowMapType::Node * Node = WindowMap.find(Event.xfocus.window);
						if(Node)
							Node->getValue()->SetActive(Event.type == FocusIn);
						break;
					}
					case MappingNotify:
					{
						XRefreshKeyboardMapping(&Event.xmapping);
						break;
					}
					case ButtonPress:
					case ButtonRelease:
					{
						WindowMapType::Node * Node = WindowMap.find(Event.xbutton.window);
						if(Node)
						{
							input::CKeyDriver * KeyDriver = static_cast<input::CKeyDriver*>(Node->getValue()->GetKeyDriver());
							input::CMouseDriver * MouseDriver = static_cast<input::CMouseDriver*>(Node->getValue()->GetMouseDriver());
							switch(Event.xbutton.button)
							{
							case Button1:
								KeyDriver->SetDown(input::KC_MOUSE_LEFT,Event.type == ButtonPress);
								break;
							case Button2:
								KeyDriver->SetDown(input::KC_MOUSE_MIDDLE,Event.type == ButtonPress);
								break;
							case Button3:
								KeyDriver->SetDown(input::KC_MOUSE_RIGHT,Event.type == ButtonPress);
								break;
							case Button4:
							case Button5:
								MouseDriver->SetMouseWheelPosition(MouseDriver->GetMouseWheelPosition() + Event.xbutton.button == Button4 ? 1.0f : -1.0f);
								break;
							}
						}
					}
					case KeyRelease:
					case KeyPress:
					{
						WindowMapType::Node * Node = WindowMap.find(Event.xkey.window);
						if(Node)
						{
							KeyMapType::Node * KeyNode = KeyMap.find(XLookupKeysym(&Event.xkey,0));
							if(KeyNode)
								static_cast<input::CKeyDriver*>(Node->getValue()->GetKeyDriver())->SetDown(KeyNode->getValue(),Event.type==KeyPress);
						}
						break;
					}
					case ClientMessage:
					{
						if (*XGetAtomName(CurrentDisplay,Event.xclient.message_type) == *"WM_DELETE_WINDOW")
						{
							WindowMapType::Node * Node = WindowMap.find(Event.xclient.window);
							if(Node)
								Node->getValue()->SetCloseFlag(true);
						}
						break;
					}
					default:
						break;
				}
			}
		}

	}
}

#endif
